Skip to content

perf: 부하테스트(2026-10)로 찾은 오류·과한 트랜잭션 수정 - #324

Merged
selee1012 merged 12 commits into
devfrom
perf/loadtest-2026-10
Oct 15, 2026
Merged

selee1012 merged 12 commits into
devfrom
perf/loadtest-2026-10

Conversation

@selee1012

Copy link
Copy Markdown
Contributor

요약

stg 를 prod 와 같은 조건(0.5 vCPU/2GB·2~4대·Hikari 15·RDS Multi-AZ·DB TLS)으로 올려 RuleUp 사용 패턴(DAU 1만·동시 500) 기준으로 부하·성능 시험을 돌리며 찾은 오류·과한 트랜잭션을 고친다. 결과·권고는 노션 「부하테스트 및 성능개선」.

커밋 (작업 단위)

커밋 무엇을 왜
test: 요청 하나의 DB 작업을 세는 계측 SqlRecorder·RequestDbProfileIT 로그인·생성 등 요청당 커넥션·커밋·SQL 수를 숫자로 본다
perf: 마지막 활동일 하루 1번 인증된 GET 마다 하던 user_activity upsert → 하루 1번, 로그인 중복 기록 제거 모든 요청이 쓰기 트랜잭션을 열고 있었다
fix: 탐색 Redis 첫 연결 재시도 기동 워밍업이 Redis 연결 전에 돌아 매번 실패 → 최대 5분 SQL 폴백 stg·prod 기동마다 재현
perf: AI 초안 LLM 호출 중 커넥션 미보유 draft() 의 @Transactional 제거 LLM 1~2초 동안 커넥션을 쥐었다
fix: sync 데드락 재시도 같은 사용자 동시 sync 가 데드락 → 트랜잭션 본문만 3회까지 재시도 피크에서 5xx 11건
perf: MySQL 드라이버 옵션을 풀 설정으로 rewriteBatchedStatements·문장 캐시 등 env 접속 주소가 기본 주소를 덮어 옵션이 빠져 있었다
fix: 예약 작업 스레드 4개 54개 작업이 스레드 1개 자정 활성화 뒤 밀린 cron 이 태스크마다 중복 실행(일별 집계 3번)
fix: 시작일 활성화 상한 제거 1분 200건 → 200건 청크로 끝까지(4분 예산) 4,870건 활성화에 22분
fix: Redis 타임아웃 2초 500ms → 2000ms CPU 포화 때 탐색 503 폭주(145건)
perf: X-Ray API 트레이스 초당 10건+10% 샘플링 규칙 ruleup-api 100% → 예약 10/s + 10% 피크에서 스팬 큐 넘침·X-Ray 429·기록 요금
fix: 통계 재계산 락 먼저 refresh() 가 방 행을 잠근 뒤 센다 먼저 센 스냅샷으로 최신 통계를 덮었다(2배 피크에서 43건)
test: k6 시나리오 infra/loadtest 재현용

결과 (피크 시나리오, 같은 데이터에서 연달아)

구성 처리량 5xx 비고
수정 전 · 0.5 vCPU 107 RPS 181 CPU 100%, p95 10~48초
수정 후 · 0.5 vCPU 92~123 RPS 0~1 여전히 CPU 100% — 코드가 아니라 CPU 가 천장
수정 후 · 1 vCPU 217 RPS(최고 348) 0 p95 ≤ 0.3초, 증설 없이 2대
1 vCPU · 2배 스트레스 312 RPS(최고 440) 0 p95 ~3.5초, RDS CPU 82%
1 vCPU · 한계점(동시 2,000) 최고 770 RPS 0 RDS CPU 96% 가 천장
1 vCPU · 장시간 40분 ~350 RPS 유지 0 메모리 평평(누수 없음)

배포 후 따로 할 일 (이 PR 밖)

  • X-Ray 샘플링 규칙은 계정 공통 — 머지 뒤 enable_tracing.py 의 규칙만 적용한다.
  • prod 권고: 태스크 1 vCPU, 메모리 오토스케일 정책 재검토, RDS 사양은 2배 피크 전에.

테스트

  • ./gradlew test 전체 통과
  • 새 IT: ChallengeStatsProjectionIT$ConcurrentRefresh(수정 전 실패 확인), VerificationSyncDeadlockRetryIT, ChallengeLifecycleBatchIT#activatesBeyondOneChunkInOneRun, ExploreWarmupRetryTest

🤖 Generated with Claude Code

selee1012 and others added 12 commits October 15, 2026 22:08
데이터소스를 감싸 커넥션 대여·커밋·SQL 문장을 기록하는 시험 전용 SqlRecorder 와, 로그인·리프레시·템플릿 초안·
생성·홈의 DB 작업을 출력하는 RequestDbProfileIT. 부하테스트(2026-10)에서 로그인·생성이 과한지 보려고 만들었다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
인증된 요청마다 user_activity upsert 가 돌아 GET 하나가 곧 DB 쓰기 하나·커넥션 대여 하나였다. 값이 날짜라
같은 날 반복은 결과가 같으므로 태스크마다 오늘 기록 여부를 기억하고 건너뛴다. 로그인·가입은 touchNow 로
반드시 쓴다(같은 날 앞선 기록이 탈퇴 상태라 무시됐을 수 있다). 로그인은 저장 전후로 두 번 쓰던 것을 저장 뒤 한 번으로.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
배포·스케일아웃마다 워밍업이 Unable to connect to Redis 로 실패해 회로가 열리고 5분 스윕까지 탐색이 SQL 폴백으로
돌았다(stg·prod 모두). 컨테이너에서 연결+PING 은 7ms 라 네트워크 문제가 아니라 0.5 vCPU 에서 막 뜬 JVM 의 첫
핸드셰이크 지연이다. 워밍업 전에 PING 을 최대 5회(2·4·6·8초 간격) 시도하고, 실패 원인은 감싼 예외가 아니라
맨 안쪽 원인까지 남긴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
draft() 가 @transactional 이라 시작 시점에 커넥션을 잡고 LLM 응답(수 초, 재시도·폴백 시 수십 초)까지 쥐고 있었다.
태스크 풀 15개 중 동시 AI 초안 수만큼이 묶인다. DB 작업은 등급 조회·초안 저장 둘뿐이라 각자 리포지토리 트랜잭션으로 충분하다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
같은 사용자의 sync 가 겹치면(재전송·백로그, 빈도 제한기가 태스크마다 메모리에 있어 두 태스크로 나뉘면 못 막는다)
세션 갱신·신호 INSERT IGNORE·판정 갱신을 하는 긴 트랜잭션이 서로의 잠금을 잡아 한쪽이 데드락으로 졌다 — 부하테스트
피크 5xx 11건 전부. 봉투 검증·빈도 제한은 한 번만 하고 트랜잭션 본문만 최대 3회 다시 돈다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stg·prod 접속 주소(env)가 기본 주소를 통째로 덮어 rewriteBatchedStatements 가 빠져 있었다 — 신호 일괄 적재가
행마다 따로 나갔다. 프로파일에서 드라이버가 CPU 의 16% 였다. 문장 캐시·세션 상태 로컬 관리·autocommit 왕복 생략을
hikari.data-source-properties 로 둬 주소와 상관없이 적용한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scheduled 54개가 기본 스레드 1개를 나눠 써서 긴 작업 뒤에 다른 작업이 밀렸다. 밀린 cron 작업은 다른 태스크의
ShedLock 최소 잠금이 풀린 뒤에 시작해 같은 날 중복 실행됐다(00:40 일별 집계가 태스크 3대에서 각각 00:40·00:41·00:43).
SCHEDULER_POOL_SIZE 로 조정한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
한 번 실행에 200건만 전환하고 끝나서, 시작일이 자정에 몰린 방 4,870건이 모두 ACTIVE 가 되기까지 22분 걸렸다.
그동안 오늘 시작인 방이 「시작 전」으로 보이고 인증이 평가되지 않는다. 200건씩 한 트랜잭션으로 끊어 남은 것이 없을
때까지(실행당 최대 4분, 락 10분 안) 돈다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
500ms 는 앱 CPU 가 포화되면 Redis 가 한가해도(엔진 CPU 5%) 넘겼고, 3번 연속이면 그 태스크의 탐색 전체가 30초간
503 이었다(가입 몰림 시드에서 145건). 진짜 장애는 2초로도 충분히 빨리 걸린다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
피크(350 RPS)에서 100% 기록은 에이전트 스팬 큐(2048)를 넘쳐 스팬을 버리고
기록 요금도 하루 ~$150 꼴이 된다. 초당 10건 예약분은 그대로라 평소 트래픽은 전부 남는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
먼저 세고 나서 락을 기다리면 그 첫 SELECT 에 REPEATABLE READ 스냅샷이 고정돼,
기다리는 동안 커밋된 가입·판정을 못 본 채 앞서 끝난 재계산을 옛 값으로 덮었다.
2배 피크 부하테스트에서 5분 보정이 43건을 고친 원인.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
실사용 비율의 피크(DAU 1만·동시 500), 2배 스트레스, 한계점, 장시간, AI 초안 시나리오와
API별 SLO 임계값. 준비(stg 로그인 우회·prod 동일 조건)·함정은 README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Test Coverage

Overall Project 82.73% -0.02% 🍏
Files changed 92.98% 🍏

File Coverage
UserActivityService.java 100% 🍏
ChallengeStatsProjectionService.java 98.04% 🍏
ChallengeActivationService.java 97.99% -2.01% 🍏
ChallengeDraftService.java 93.1% 🍏
LoginSessionService.java 89.29% 🍏
VerificationSyncService.java 86.59% -0.29% 🍏
ExploreRedisStore.java 81.15% 🍏
AuthService.java 78.37% 🍏
ExploreIndexJobs.java 60.44% -3.19% 🍏

@selee1012
selee1012 merged commit 8e06890 into dev Oct 15, 2026
3 checks passed
@selee1012
selee1012 deleted the perf/loadtest-2026-10 branch October 15, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant